package com.atguigu.yygh.hosp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateRange;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.yygh.common.exception.YyghException;
import com.atguigu.yygh.hosp.dao.DepartmentRepository;
import com.atguigu.yygh.hosp.dao.ScheduleRepository;
import com.atguigu.yygh.hosp.service.HospitalService;
import com.atguigu.yygh.hosp.service.ScheduleService;
import com.atguigu.yygh.model.hosp.BookingRule;
import com.atguigu.yygh.model.hosp.Department;
import com.atguigu.yygh.model.hosp.Hospital;
import com.atguigu.yygh.model.hosp.Schedule;
import com.atguigu.yygh.vo.hosp.BookingScheduleRuleVo;
import com.atguigu.yygh.vo.hosp.ScheduleOrderVo;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 孔德成
 * @slogan 致敬大师，致敬未来的你
 * @date 2021/9/4 6:53 上午
 * @desc
 */
@Service
public class ScheduleServiceImpl implements ScheduleService {

    private final ScheduleRepository scheduleRepository;
    private final HospitalService hospitalService;
    private final DepartmentRepository departmentRepository;

    public ScheduleServiceImpl(ScheduleRepository scheduleRepository, HospitalService hospitalService, DepartmentRepository departmentRepository) {
        this.scheduleRepository = scheduleRepository;
        this.hospitalService = hospitalService;
        this.departmentRepository = departmentRepository;
    }

    @Override
    public void save(Map<String, Object> map) {
        // 1. 将 map 转为 json 字符串，再将 json 字符串转为 bean 对象
        Schedule scheduleSave = JSONObject.parseObject(JSONObject.toJSONString(map), Schedule.class);
        // 2. 根据医院编号和排班编号查询排班信息
        Schedule scheduleQuery = scheduleRepository.findScheduleByHoscodeAndHosScheduleId(scheduleSave.getHoscode(), scheduleSave.getHosScheduleId());
        // 3. 如果排班不存在，说明是第一次上传
        if (ObjectUtil.isNull(scheduleQuery)) {
            // 初始化排班的其他信息：状态，逻辑删除字段，创建时间，更新时间，
            // 0 未删除；1 删除
            scheduleSave.setIsDeleted(0);
            scheduleSave.setCreateTime(new Date());
            scheduleSave.setUpdateTime(scheduleSave.getCreateTime());
        } else {
            // 排班存在，使用已存在信息填充其他空字段，执行更新操作
            scheduleSave.setId(scheduleQuery.getId());
            scheduleSave.setIsDeleted(scheduleQuery.getIsDeleted());
            scheduleSave.setCreateTime(scheduleQuery.getCreateTime());
            scheduleSave.setUpdateTime(new Date());
        }
        // 4. 保存（插入MongoDB）
        scheduleRepository.save(scheduleSave);
    }

    @Override
    public Page<Schedule> selectPage(Map<String, Object> map) {
        // 参数校验
        Object page = map.get("page");
        if (ObjectUtil.isNull(page)) {
            page = 1;
        }
        Object limit = map.get("limit");
        if (ObjectUtil.isNull(limit)) {
            limit = 10;
        }

        // 根据排班日期升序排序
        Sort sort = Sort.by(Sort.Direction.ASC, "workDate");
        // mongodb 0 为第一页
        PageRequest pageOf = PageRequest.of(Convert.toInt(page) - 1, Convert.toInt(limit), sort);

        // 创建查询条件, 根据医院编号查询所有排班
        Schedule schedule = new Schedule();
        schedule.setHoscode(Convert.toStr(map.get("hoscode")));
        schedule.setDepcode(Convert.toStr(map.get("depcode")));
        Example<Schedule> exampleOf = Example.of(schedule);

        return scheduleRepository.findAll(exampleOf, pageOf);
    }

    @Override
    public void remove(String hoscode, String hosScheduleId) {
        Schedule schedule = scheduleRepository.findScheduleByHoscodeAndHosScheduleId(hoscode, hosScheduleId);
        if (ObjectUtil.isNotNull(schedule)) {
            scheduleRepository.deleteById(schedule.getId());
        }
    }

    @Override
    public Map<String, Object> getRuleSchedule(long page, long limit, String hoscode, String depcode) {
        HashMap<String, Object> map = new HashMap<>(4);
        map.put("page", page);
        map.put("limit", limit);
        map.put("hoscode", hoscode);
        map.put("depcode", depcode);
        Page<Schedule> schedules = selectPage(map);
        List<Schedule> content = schedules.getContent();
        List<BookingScheduleRuleVo> collect = content.stream()
                .map(schedule -> {
                    BookingScheduleRuleVo bookingScheduleRuleVo = BeanUtil.toBean(schedule, BookingScheduleRuleVo.class);
                    Date workDate = bookingScheduleRuleVo.getWorkDate();
                    // 将日期转为星期，使用前缀周
                    bookingScheduleRuleVo.setDayOfWeek(DateUtil.dayOfWeekEnum(workDate).toChinese("周"));
                    return bookingScheduleRuleVo;
                })
                .collect(Collectors.toList());
        HashMap<String, Object> result = new HashMap<>(3);
        result.put("bookingScheduleList", collect);
        result.put("total", schedules.getTotalElements());
        // 查询医院名称
        String hospName = hospitalService.getHospName(hoscode);
        HashMap<String, Object> baseMap = new HashMap<>(1);
        baseMap.put("hosname", hospName);
        result.put("baseMap", baseMap);
        return result;
    }

    @Override
    public List<Schedule> getDetailSchedule(String hoscode, String depcode, String workDate) {
        // 创建查询条件, 根据医院编号查询所有排班
        Schedule schedule = new Schedule();
        schedule.setHoscode(hoscode);
        schedule.setDepcode(depcode);
        schedule.setWorkDate(DateUtil.parseDate(workDate));
        Example<Schedule> exampleOf = Example.of(schedule);
        return scheduleRepository.findAll(exampleOf);
    }

    @Override
    public Map<String, Object> getScheduleDate(long page, long limit, String hoscode, String depcode) {
        // 1. 根据医院编号和科室编号查询所有挂号日期数据
        List<Schedule> schedules = scheduleRepository.findSchedulesByHoscodeAndDepcode(hoscode, depcode);

        // 2. 根据医院编号查询医院信息
        Hospital hospital = hospitalService.getByHoscode(hoscode);
        // 3. 获取预约规则
        BookingRule bookingRule = hospital.getBookingRule();
        // 4. 根据预约周期生成日期范围生成器

        // 预约周期
        Integer cycle = bookingRule.getCycle();
        // 放号时间
        DateTime releaseTime = DateUtil.parse(bookingRule.getReleaseTime());
        // 停止挂号时间
        DateTime stopTime = DateUtil.parse(bookingRule.getStopTime());

        // 开始时间
        DateTime start = DateUtil.date();
        // 如果当前时间未超过截止时间
        if (DateUtil.compare(start, DateUtil.parse(bookingRule.getStopTime())) < 0) {
            cycle -= 1;
        }
        // 结束时间
        DateTime end = DateUtil.offsetDay(DateUtil.date(), cycle);
        DateRange range = DateUtil.range(start, end, DateField.DAY_OF_YEAR);

        // 5. 根据排班日期分组
        Map<String, List<Schedule>> collect = schedules.stream()
                .collect(Collectors.groupingBy(schedule -> DateUtil.formatDate(schedule.getWorkDate()), Collectors.toList()));

        // 6. 遍历日期数据
        List<BookingScheduleRuleVo> scheduleList = new ArrayList<>();
        range.forEach(dateTime -> {
            BookingScheduleRuleVo bookingScheduleRuleVo = new BookingScheduleRuleVo();
            String workDate = DateUtil.formatDate(dateTime);
            List<Schedule> list = collect.getOrDefault(workDate, new ArrayList<>());
            // 设置排班日期
            bookingScheduleRuleVo.setWorkDate(dateTime);
            // 设置星期
            bookingScheduleRuleVo.setDayOfWeek(DateUtil.dayOfWeekEnum(dateTime).toChinese("周"));
            // 设置就诊医生人数
            bookingScheduleRuleVo.setDocCount(list.size());
            if (bookingScheduleRuleVo.getDocCount() > 0) {
                // 设置可预约数
                bookingScheduleRuleVo.setReservedNumber(list.stream().map(Schedule::getReservedNumber).reduce(Integer::sum).get());
                // 设置剩余数
                bookingScheduleRuleVo.setAvailableNumber(list.stream().map(Schedule::getAvailableNumber).reduce(Integer::sum).get());
            } else {
                // 没有医生，说明当天无排班
                bookingScheduleRuleVo.setReservedNumber(-1);
                bookingScheduleRuleVo.setAvailableNumber(-1);
            }

            // 设置日期状态,根据放号时间设置状态
            // 1. 当天：在放号时间内，设置为正常，超过放号时间，设置停止挂号，未到放号时间，显示即将放号
            // 2. 其他日期：24：00-8：30 设置停止挂号，8：30后设置正常
            // 3. 预约周期内最后一天：设置即将放号
            if (DateUtil.isSameDay(dateTime, start)) {
                if (DateUtil.compare(DateTime.now(), stopTime) > 0) {
                    bookingScheduleRuleVo.setStatus(-1);
                } else if (DateUtil.compare(DateTime.now(), releaseTime) < 0) {
                    bookingScheduleRuleVo.setStatus(1);
                } else {
                    bookingScheduleRuleVo.setStatus(0);
                }
            } else if (DateUtil.isSameDay(dateTime, end)) {
                bookingScheduleRuleVo.setStatus(1);
            } else {
                if (DateUtil.isIn(DateTime.now(), DateUtil.parse(bookingRule.getReleaseTime()), DateUtil.endOfDay(DateTime.now()))) {
                    bookingScheduleRuleVo.setStatus(0);
                } else {
                    bookingScheduleRuleVo.setStatus(-1);
                }
            }
            scheduleList.add(bookingScheduleRuleVo);

        });


        List<BookingScheduleRuleVo> collect1 = scheduleList.stream()
                // 分页查询
                .skip((page - 1) * limit)
                .limit(limit)
                .collect(Collectors.toList());


        Map<String, Object> result = new HashMap<>(3);
        result.put("bookingScheduleList", collect1);
        result.put("total", bookingRule.getCycle());

        // 查询科室
        Department department = departmentRepository.findDepartmentByHoscodeAndDepcode(hoscode, depcode);
        HashMap<String, Object> baseMap = new HashMap<>(6);
        baseMap.put("hosname", hospital.getHosname());
        baseMap.put("bigname", department.getBigname());
        baseMap.put("depname", department.getDepname());
        baseMap.put("workDateString", DateUtil.format(DateUtil.date(), "yyyy年MM月"));
        baseMap.put("releaseTime", bookingRule.getReleaseTime());
        result.put("baseMap", baseMap);
        return result;
    }

    @Override
    public Schedule getScheduleById(String id) {
        Schedule schedule = scheduleRepository.findById(id).get();
        Department department = departmentRepository.findDepartmentByHoscodeAndDepcode(schedule.getHoscode(), schedule.getDepcode());

        schedule.getParam().put("hosname", hospitalService.getHospName(schedule.getHoscode()));
        schedule.getParam().put("depname", department.getDepname());
        schedule.getParam().put("dayOfWeek", DateUtil.dayOfWeekEnum(schedule.getWorkDate()).toChinese("周"));
        return schedule;
    }

    @Override
    public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
        ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();
        // 1. 查询排班信息
        Optional<Schedule> scheduleOpt = scheduleRepository.findById(scheduleId);
        if (!scheduleOpt.isPresent()) {
            throw new YyghException(20001, "排班不存在");
        }
        Schedule schedule = scheduleOpt.get();
        // 2. 查询预约规则
        Hospital hospital = hospitalService.getByHoscode(schedule.getHoscode());
        BookingRule bookingRule = hospital.getBookingRule();

        // 复制部分同名属性
        BeanUtil.copyProperties(schedule, scheduleOrderVo);
        // 设置其他属性
        scheduleOrderVo.setHosname(hospital.getHosname());
        scheduleOrderVo.setReserveDate(schedule.getWorkDate());
        scheduleOrderVo.setReserveTime(schedule.getWorkTime());
        String depname = departmentRepository.findDepartmentByHoscodeAndDepcode(hospital.getHoscode(), schedule.getDepcode()).getDepname();
        scheduleOrderVo.setDepname(depname);

        // TODO 需求不明确
        // 退号截止天数（如：就诊前一天为-1，当天为0）
        // 预约日期 9/13，退号截止天数 -1
        // 9月12号之前可以退号，之后不能退号
        int quitDay = bookingRule.getQuitDay();
        scheduleOrderVo.setQuitTime(DateUtil.offsetDay(schedule.getWorkDate(), quitDay));


        // TODO 这3个属性意义不明，数据库中没有存储该字段
        // 预约开始时间
//        scheduleOrderVo.setStartTime(DateUtil.parse(bookingRule.getReleaseTime()));
        // 预约截止时间
//        scheduleOrderVo.setEndTime();
        // 当天停止挂号时间
//        scheduleOrderVo.setStopTime(DateUtil.parse(bookingRule.getStopTime()));
        return scheduleOrderVo;
    }

    /**
     * 更新排班
     *
     * @param schedule
     */
    @Override
    public void update(Schedule schedule) {
        schedule.setUpdateTime(new Date());
        scheduleRepository.save(schedule);
    }
}
